/*
 * DropTargetImpl.cpp
 *
 *  Created on: 2012-9-2
 *      Author: yangentao@gmail.com
 */

#include "DropTargetImpl.h"
namespace yet{
void PositionCursor(HWND hwndEdit, POINTL pt) {
	DWORD curpos;
	ScreenToClient(hwndEdit, (POINT *) &pt);
	curpos = SendMessage(hwndEdit, EM_CHARFROMPOS, 0, MAKELPARAM(pt.x, pt.y) );

	// set cursor position
	SendMessage(hwndEdit, EM_SETSEL, LOWORD(curpos), LOWORD(curpos) );
}
void DropData(HWND hwnd, IDataObject *pDataObject) {
	// construct a FORMATETC object
	FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
	STGMEDIUM stgmed;

	// See if the dataobject contains any TEXT stored as a HGLOBAL
	if (pDataObject->QueryGetData(&fmtetc) == S_OK ) {
		// Yippie! the data is there, so go get it!
		if (pDataObject->GetData(&fmtetc, &stgmed) == S_OK ) {
			// we asked for the data as a HGLOBAL, so access it appropriately
			PVOID data = GlobalLock(stgmed.hGlobal);

			//SetWindowText(hwnd, (char *) data);
			SendMessageA(hwnd, EM_REPLACESEL, TRUE, (LPARAM) data);

			GlobalUnlock(stgmed.hGlobal);

			// release the data using the COM API
			ReleaseStgMedium(&stgmed);
		}
	}
}
DropTargetImpl::DropTargetImpl(HWND hWnd_) {
	refCount = 1;
	hWnd = hWnd_;
	allowDrop = false;
}

DropTargetImpl::~DropTargetImpl() {
}
ULONG STDMETHODCALLTYPE DropTargetImpl::AddRef(void) {
	return InterlockedIncrement(&refCount);
}

ULONG STDMETHODCALLTYPE DropTargetImpl::Release(void) {
	LONG r = InterlockedDecrement(&refCount);
	if (r == 0) {
		delete this;
		return 0;
	} else {
		return r;
	}
}
HRESULT STDMETHODCALLTYPE DropTargetImpl::QueryInterface(REFIID riid, void **ppv) {
	*ppv = NULL;
	if (riid == IID_IUnknown || riid == IID_IDropTarget) {
		*ppv = this;
		AddRef();
		return S_OK;
	} else {
		return E_NOINTERFACE;
	}
}
HRESULT STDMETHODCALLTYPE DropTargetImpl::DragEnter(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
	allowDrop = QueryDataObject(pDataObject);
	if (allowDrop) {
		*pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
		SetFocus(hWnd);
		PositionCursor(hWnd, pt);
	} else {
		*pdwEffect = DROPEFFECT_NONE;
	}
	return S_OK ;
}
HRESULT STDMETHODCALLTYPE DropTargetImpl::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
	if (allowDrop) {
		*pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
		PositionCursor(hWnd, pt);
	} else {
		*pdwEffect = DROPEFFECT_NONE;
	}

	return S_OK ;
}
HRESULT STDMETHODCALLTYPE DropTargetImpl::DragLeave(void) {
	return S_OK ;
}
HRESULT STDMETHODCALLTYPE DropTargetImpl::Drop(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
	PositionCursor(hWnd, pt);
	if (allowDrop) {
		DropData(hWnd, pDataObject);
		*pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
	} else {
		*pdwEffect = DROPEFFECT_NONE;
	}
	return S_OK ;
}

DWORD DropTargetImpl::DropEffect(DWORD grfKeyState, POINTL pt, DWORD dwAllowed) {
	DWORD dwEffect = 0;
	// 1. check "pt" -> do we allow a drop at the specified coordinates?
	// 2. work out that the drop-effect should be based on grfKeyState
	if (grfKeyState & MK_CONTROL) {
		dwEffect = dwAllowed & DROPEFFECT_COPY;
	} else if (grfKeyState & MK_SHIFT) {
		dwEffect = dwAllowed & DROPEFFECT_MOVE;
	}

	// 3. no key-modifiers were specified (or drop effect not allowed), so
	//    base the effect on those allowed by the dropsource
	if (dwEffect == 0) {
		if (dwAllowed & DROPEFFECT_COPY) dwEffect = DROPEFFECT_COPY;
		if (dwAllowed & DROPEFFECT_MOVE) dwEffect = DROPEFFECT_MOVE;
	}

	return dwEffect;
}
bool DropTargetImpl::QueryDataObject(IDataObject *pDataObject) {
	FORMATETC f = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
	return pDataObject->QueryGetData(&f) == S_OK ;
}

IDropTarget* RegisterDropWindow(HWND hwnd) {
	IDropTarget *pDropTarget = new DropTargetImpl(hwnd);
	CoLockObjectExternal(pDropTarget, TRUE, FALSE);
	RegisterDragDrop(hwnd, pDropTarget);
	return pDropTarget;
}

void UnregisterDropWindow(HWND hwnd, IDropTarget *pDropTarget) {
	RevokeDragDrop(hwnd);
	CoLockObjectExternal(pDropTarget, FALSE, TRUE);
	// release our own reference
	pDropTarget->Release();
}
}

